import * as THREE from "../../../modules/three/three.module.js";
import {createPlaneBySvd} from "../lib/gadget/PlaneRefMesh.js";
import {findConvexHull} from "../lib/util/StaticMethods.js";
import {arrAintersectB, arrAsubB} from "../lib/util/ToolMethods.js";

const _quat = new THREE.Quaternion();
const _matRot = new THREE.Matrix4();
const _matInv = new THREE.Matrix4();
const unitZ = new THREE.Vector3(0, 0, 1);

function getMatsByPointAndNormal(point, normal) {
  _quat.setFromUnitVectors(unitZ, normal);
  _matRot.makeRotationFromQuaternion(_quat);
  _matRot.setPosition(point);
  _matInv.copy(_matRot).invert();
  // const arr0 = [a, b, c];
  // const arr = arr0.map(it => it.clone().applyMatrix4(_matInv))
  return {mat23: _matRot, mat32: _matInv};
}

function trianglesToPoints(triangles) {
  const indices = [];
  const points = [];
  triangles.forEach(triangle => {
    triangle.indices.forEach((num, index) => {
      if (!indices.includes(num)) {
        indices.push(num);
        points.push(triangle.points[index]);
      }
    });
  });
  return {points, indices};
}

function getCameraState(cameraStates, indexA, indexB) {
  const diff = Math.abs(indexA - indexB);
  const isEdge = diff === 1;
  const isLastEdge = diff === cameraStates.length - 1;
  if (isEdge) {
    return cameraStates[Math.min(indexA, indexB)]
  } else if (isLastEdge) {
    return cameraStates[cameraStates.length - 1];
  } else {
    return cameraStates[Math.max(indexA, indexB)];
  }
}

function findNextTriangle(convexTriangles, trianglesAll) {
  const triangles = arrAsubB(trianglesAll, convexTriangles);
  for (let i = 0; i < triangles.length; i++) {
    const triangle = triangles[i];
    if (convexTriangles.find(it => it.shareEdgeWith(triangle))) {
      const {points} = trianglesToPoints(convexTriangles.concat([triangle]));
      const convexPts = findConvexHull(points);
      if (points.length === convexPts.length) {
        return triangle;
      }
    }
  }
  return null;
}

export function createParams(cutPoints, cameraStates) {
  const params = []
  for (let i = 0; i < cutPoints.length; i++) {
    const a = cutPoints[i];
    const b = i === cutPoints.length - 1 ? cutPoints[0] : cutPoints[i+1];
    const arr = cameraStates[i];
    const position = new THREE.Vector3(arr[0], arr[1], arr[2]);
    const rotation = new THREE.Euler(arr[3], arr[4], arr[5]);
    const param = [a, b, position, rotation];
    params.push(param);
  }
  return params // [[params]]
}

class TriangleObj {
  a = new THREE.Vector3()
  b = new THREE.Vector3()
  c = new THREE.Vector3()
  // 三角有几条是与 轮廓重合的，0 或 1 或 2
  rawEdgeCount = 0
  indices = []
  points = []

  constructor(points, face) {
    this.indices = face
    const [ia, ib, ic] = face;
    this.a.copy(points[ia])
    this.b.copy(points[ib])
    this.c.copy(points[ic])
    this.points = [this.a, this.b, this.c]
    const count = points.length - 1;
    const ab = Math.abs(ia - ib);
    const bc = Math.abs(ib - ic);
    const ca = Math.abs(ic - ia);
    if (ab === 1 || ab === count) {
      this.rawEdgeCount++;
    }
    if (bc === 1 || bc === count) {
      this.rawEdgeCount++;
    }
    if (ca === 1 || ca === count) {
      this.rawEdgeCount++;
    }
  }

  shareEdgeWith(other) {
    return arrAintersectB(other.indices, this.indices).length === 2
  }
}

class PlaneProject3dTo2d {
  _mat32 = new THREE.Matrix4()
  contourPoints = []
  _center = new THREE.Vector3()

  initPlaneProjection(cutPoints) {
    const plane  = createPlaneBySvd(cutPoints);
    plane.coplanarPoint(this._center)
    const {mat32, mat23} = getMatsByPointAndNormal(this._center, plane.normal);
    this._mat32.copy(mat32);
    this.contourPoints = cutPoints.map(it => it.clone().applyMatrix4(mat32));

    return this;
  }
}

export default class CutPointsDivider {
  /**
   * 把 cutPoints0 表示的切割轮廓，划分为尽量少的凸形切割；cutPoints0本身为凸形，返回空数组
   * @param {THREE.Vector3[]} cutPoints0
   * @param {number[][]} cameraStates0 六个数字为一组，前三个数字是相机的三维坐标，后三个数字是相机的姿态
   * @return {*[][]} 数组的每个元素为 [lineStart, lineEnd, cameraPosition, cameraRotation]
   */
  static divide(cutPoints0, cameraStates0) {
    if (cutPoints0.length < 4) {
      return [];
    }
    const cutPoints = cutPoints0.slice();
    const cameraStates = cameraStates0.slice();
    const projectTool = new PlaneProject3dTo2d().initPlaneProjection(cutPoints);
    const convexHull = findConvexHull(projectTool.contourPoints);
    if (convexHull.length === cutPoints.length) {
      // cutPoints0 投影到平面后是凸多边形
      return [];
    }
    if (!THREE.ShapeUtils.isClockWise(convexHull)) {
      projectTool.contourPoints.reverse();
      cutPoints.reverse();
      cameraStates.reverse();
    }
    const faces = THREE.ShapeUtils.triangulateShape(projectTool.contourPoints, []);
    let triangleObjs = [];
    faces.forEach(face => {
      triangleObjs.push(new TriangleObj(projectTool.contourPoints, face));
    });
    const triangleGroups = [];
    const first = triangleObjs.find(it => it.rawEdgeCount === 2);
    let triangleGroup = [first];
    while (triangleObjs.length) {
      const tri = findNextTriangle(triangleGroup, triangleObjs);
      if (tri) {
        triangleGroup.push(tri);
      } else {
        triangleGroups.push(triangleGroup);
        triangleObjs = arrAsubB(triangleObjs, triangleGroup);
        if (triangleObjs.length) {
          triangleGroup = [triangleObjs[0]];
        }
      }
    }
    const paramsLst = [];
    // 绑定相机状态
    triangleGroups.forEach(group => {
      const {points, indices} = trianglesToPoints(group);
      const hull = findConvexHull(points);
      const convexPoints = [];
      const convexIndex = [];
      hull.forEach(it => {
        const ind = points.findIndex(pt => {
          return Math.abs(pt.x - it.x) <= 0.001 && Math.abs(pt.y - it.y) <= 0.001
        });
        convexIndex.push(indices[ind]);
        convexPoints.push(points[ind]);
      });
      const params = [];
      for (let i = 0; i < convexIndex.length; i++) {
        const a = convexIndex[i];
        const b = i === convexIndex.length - 1 ? convexIndex[0] : convexIndex[i+1];
        const arr = getCameraState(cameraStates, a, b);
        const position = new THREE.Vector3(arr[0], arr[1], arr[2]);
        const rotation = new THREE.Euler(arr[3], arr[4], arr[5]);
        const param = [cutPoints[a], cutPoints[b], position, rotation];
        params.push(param);
      }
      paramsLst.push(params);
    });
    return paramsLst;
  }
}
